#import "NetworkManager.h"
#import "GamePlay.h"
#import "GameKitManager.h"
#import "MultipeerConnectivityManager.h"

#import "AppDelegate.h"
#import "WaitingView.h"

@interface NetworkManager()

@property(nonatomic) NSString *startLookingForOpponent;
@property(nonatomic) int playerNumber;
@property(nonatomic) int playerlastUpdate;
@property(nonatomic) int peerlastUpdate;

@property(nonatomic) GamePlay *gamePlay;
@property(nonatomic) id connectivityManager;

@property(nonatomic) id receivedDataObserver;
@property(nonatomic) id connectedPeerObserver;
@property(nonatomic) id disconnectionObserver;
@property(nonatomic) id enteredBackgroundObserver;
@property(nonatomic) id playerChoosingCharacterObserver;

@property(nonatomic) BOOL isEnteredBackground;

@property(nonatomic) NSDate *sentMessageForPing;
//@property(nonatomic) NSDate *receivedMessageForPing;

@end


@implementation NetworkManager

static BOOL isInited;

enum {
    gkMessageStartingSessionDate,
    gkMessageRandomGeneratedNumber,
    gkMessageCharacterAndLevelToPlay,
    gkMessageReadyToStart,
    gkMessagePositionOfEnemy,
    gkMessageFirstPing,
    gkMessageSecondPing
};


#pragma mark - init a Singleton object
+ (id)sharedInstance
{
    static NetworkManager *instance = nil;
    if ( !instance ) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            instance = [[super alloc] init];
        });
    }
    return instance;
}

+ (id)alloc
{
    return [NetworkManager sharedInstance];
}

- (id)init
{
    if ( !isInited ){
        self = [super init];
        if ( self ) {
            self.receivedDataObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kNetworkManagerNotificationNameForReceivedData object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
                //only for testing purposes
                NSLog(@"Received data - NetworkManager");
                if ( [note.object isKindOfClass:[NSData class]] )
                {
                    [self processReceivedData:note.object];
                }else{
                    NSLog(@"received data is not NSData class");
                }
            }];
            
            self.connectedPeerObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kNetworkNotificationNameForConnectedPeer object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {

                NSLog(@"connected to peer - NetworkManager");
                
                [self sendingStartingTimeToFindWhoIsFirst];
                
            }];
            
            self.disconnectionObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kNetworkManagerNotificationNameForDisconnecting object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
                
                [self opponentWasDisconnected];
                NSLog(@"disconnecting - NetworkManager");
            }];
            
            self.enteredBackgroundObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kNetworkNotificationNameForEnteredBackground object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
                self.isEnteredBackground = YES;
                
                if ( self.isBrowserPresented )
                    [self.connectivityManager dismissBrowser];
                else
                    [self endSession];
                NSLog(@"disconnecting - NetworkManager");
            }];
            
            self.playerChoosingCharacterObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kNetworkNotificationNamePlayerChoosingCharacter object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
                self.isEnteredBackground = YES;
                
                [self sendMessageForPing];
                NSLog(@"player is choosing - NetworkManager");
            }];
            
            self.gamePlay = [GamePlay sharedInstance];
            self.isAlertViewDisplayedEndConnection = NO;

            if ( kIsOS7OrLater ){
                self.connectivityManager = [MultipeerConnectivityManager sharedInstance];
            }else{
                self.connectivityManager = [GameKitManager sharedInstance];
            }
        }
    }
	return self;
}

-(void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self.receivedDataObserver];
    [[NSNotificationCenter defaultCenter] removeObserver:self.connectedPeerObserver];
    [[NSNotificationCenter defaultCenter] removeObserver:self.disconnectionObserver];
    [[NSNotificationCenter defaultCenter] removeObserver:self.enteredBackgroundObserver];
    [[NSNotificationCenter defaultCenter] removeObserver:self.playerChoosingCharacterObserver];
}

-(id)copy{
    return [NetworkManager sharedInstance];
}

-(id)mutableCopy{
    return [NetworkManager sharedInstance];
}

#pragma mark - Network
- (void)startLookingForOponent
{
    if ( self.gamePlay.gameState == kGameStateLaunchedMultiPlayer ) {
        NSLog(@"Start looking for opponent - NetworkManager");
        
        self.startLookingForOpponent = [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterFullStyle];
        NSLog(@"self.startLookingForOpponent = %@", self.startLookingForOpponent);
        
        self.gamePlay.gameState = kGameStateLookingForOpponent;
        [self.connectivityManager startLookingForOponent];
        
        self.isBrowserPresented = YES;
        self.ping = 0;
    }
}

//receive data
- (void)processReceivedData:(NSData *)data {
    
    const char *incomingPacket = (const char *)[data bytes];
    char messageType = incomingPacket[0];
    NSLog(@"receive some data");

    
    switch (messageType) {
        case gkMessageStartingSessionDate:
        {
            NSData *receivedDataString = [data subdataWithRange:NSMakeRange(1, [data length]-1) ];
            NSString *opponentStartingDate = [[NSString alloc] initWithData:receivedDataString encoding:NSUTF8StringEncoding];
            NSLog(@"opponentStartingDate = %@",opponentStartingDate);
            
            [self checkWhoIsFirstBasedOnReceivedDate:opponentStartingDate];
            break;
        }
        case gkMessageRandomGeneratedNumber:
        {
            int peerGeneratedNumber = *(int *)(incomingPacket + 1);
            NSLog(@"received number - peerGeneratedNumber= %d", peerGeneratedNumber);
            [self checkWhoIsFirstBasedOnReceivedNumber:peerGeneratedNumber];
            break;
        }
        case gkMessageCharacterAndLevelToPlay:
        {
            int currentLevel = *(int *)(incomingPacket + 1);

            int currentCharacter = *(int *)(incomingPacket + 1+sizeof(int));

            //sending currentLevel and currentCharacter to GameScene
            self.gamePlay.characterType = currentCharacter;
            self.gamePlay.level = currentLevel;
            
            [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkNotificationNamePlayerIsReadyToStart object:nil];
            
             NSLog(@"self.gamePlay.level = %d, self.gamePlay.characterType =%d", self.gamePlay.level, self.gamePlay.characterType);
            break;
        }
        case gkMessageReadyToStart:
        {

            //sending currentLevel and currentCharacter to GameScene
            [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkNotificationNameOpponentIsReadyToStart object:nil];
            
            NSLog(@"Ready to start");
            break;
        }
            
        case gkMessagePositionOfEnemy:
        {
            int currentUpdate = *(int *)(incomingPacket + 1);
            
            if ( currentUpdate <= self.peerlastUpdate ) {
                return;
            }
            self.peerlastUpdate = currentUpdate;

            //preparing data for enemyInfo
            EnemyInfo enemyInfo;
            enemyInfo.state = *(int *)(incomingPacket + 1 + sizeof(int));
            enemyInfo.position.x = *(float *)(incomingPacket + 1 + sizeof(int)+ sizeof(int));
            enemyInfo.position.y = *(float *)(incomingPacket + 1 + sizeof(int)+ sizeof(int)+sizeof(float));
            
            NSData *data = [NSData dataWithBytes:&enemyInfo length:sizeof(enemyInfo)];

            [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkNotificationNameForReceivedData object:data];
            
            NSLog(@"new position");
            break;
        }
        case gkMessageFirstPing:
        {
            NSMutableData *data = [NSMutableData dataWithCapacity:1];
            char messageType = gkMessageSecondPing;
            [data appendBytes:&messageType length:1];
            [self.connectivityManager sendData:data withMode:reliable];
            
            NSLog(@"Received - data for ping");
        }
        case gkMessageSecondPing:
        {
            NSDate *receivedData = [NSDate date];
            NSLog(@"self.receivedMessageForPing = %@", receivedData);
            
            self.ping = [receivedData timeIntervalSinceDate:self.sentMessageForPing];
            [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkNotificationNamePingIsReceived object:nil];
            

            NSLog(@"ping = %f", self.ping);
        }
    }
}

#pragma mark - set who plays first - GameKit
- (void)sendingStartingTimeToFindWhoIsFirst
{
    self.gamePlay.gameState = kGameStateLookingWhoIsFirst;
    
    NSData* dataString = [self.startLookingForOpponent dataUsingEncoding:NSUTF8StringEncoding];
    NSUInteger lenDataString = [dataString length];
    
    NSMutableData *data = [NSMutableData dataWithCapacity:1+lenDataString];
    
    char messageType = gkMessageStartingSessionDate;
    [data appendBytes:&messageType length:1];
    [data appendData:dataString];
    
    NSLog(@"sendingStartingTimeToFindWhoIsFirst: sending data = %@",data);
    [self.connectivityManager sendData:data withMode:reliable];
}

- (void)sendingRandomGeneratedNumberToFindWhoIsFirst
{
    NSMutableData *data = [NSMutableData dataWithCapacity:1+sizeof(int)];
    char messageType = gkMessageRandomGeneratedNumber;
    [data appendBytes:&messageType length:1];

    int generatedNumber = arc4random();
    self.playerNumber = generatedNumber;
    NSLog(@" self.playerNumber = %d",  self.playerNumber);
    [data appendBytes:&generatedNumber length:sizeof(int)];

    
    NSLog(@"sendingRandomGeneratedNumberToFindWhoIsFirst: sending data = %@",data);
    [self.connectivityManager sendData:data withMode:reliable];
}

- (void)checkWhoIsFirstBasedOnReceivedNumber:(int)opponentNumber
{
    NSLog(@"self.playerNumber =%d, opponentNumber = %d", self.playerNumber,opponentNumber);
    
    if ( self.playerNumber == opponentNumber ) {
        NSLog(@"Equal");
        [self sendingRandomGeneratedNumberToFindWhoIsFirst];
        return;
    }
    else if ( self.playerNumber > opponentNumber ) {
        NSLog(@"self.playerNumber > opponentNumber");
        self.gamePlay.gameState = kGameStatePlayerChoosingLevelAndCharacter;
        //posting a notification
        [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkNotificationNamePlayerChoosingCharacter object:nil];
    }
    else {
        NSLog(@"self.playerNumber < opponentNumber");
        self.gamePlay.gameState = kGameStatePlayerWaitsForLevelAndCharacter;
        //posting a notification
        [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkNotificationNamePlayerWaitingForEnemyForChoosingCharacter object:nil];
    }
}

- (void)checkWhoIsFirstBasedOnReceivedDate:(NSString *)date
{
    NSDateFormatter *dateFormater = [[NSDateFormatter alloc] init];
    [dateFormater setDateStyle:NSDateFormatterShortStyle];
    [dateFormater setTimeStyle:NSDateFormatterFullStyle];
    
    NSDate *startedDateOpponent = [dateFormater dateFromString:date];
    NSDate *startedDatePlayer = [dateFormater dateFromString:self.startLookingForOpponent];
    
    NSLog(@"startedDatePlayer = %@, startedDateOpponent = %@", startedDatePlayer,startedDateOpponent);
    
    switch ([startedDatePlayer compare:startedDateOpponent])
    {
        case NSOrderedAscending:
            NSLog(@"startedDatePlayer");
            // startedDatePlayer is earlier in time than startedDateOpponent
            self.gamePlay.gameState = kGameStatePlayerChoosingLevelAndCharacter;
            //posting a notification
            [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkNotificationNamePlayerChoosingCharacter object:nil];
            
            break;
            
        case NSOrderedSame:
            NSLog(@"Equal");
            // The dates are the same
            [self sendingRandomGeneratedNumberToFindWhoIsFirst];
            
            return;
        case NSOrderedDescending:
            // startedDatePlayer is later in time than startedDateOpponent
            NSLog(@"startedDateOpponent");
            self.gamePlay.gameState = kGameStatePlayerWaitsForLevelAndCharacter;
            
            //posting a notification
            [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkNotificationNamePlayerWaitingForEnemyForChoosingCharacter object:nil];
            
            break;
    }
}

#pragma mark - sending message for ping
- (void)sendMessageForPing
{
    self.sentMessageForPing = [NSDate date];
    
    NSLog(@"Sending message for starting ping, self.sentMessageForPing = %@", self.sentMessageForPing);
    
    NSMutableData *data = [NSMutableData dataWithCapacity:1];
    char messageType = gkMessageFirstPing;
    [data appendBytes:&messageType length:1];
    
    [self.connectivityManager sendData:data withMode:reliable];
}

#pragma mark - sending data - enemyInfo and Level&Character
- (void)sendEnemyInformation:(EnemyInfo)enemyInfo
{
    NSMutableData *data = [NSMutableData dataWithCapacity:1+sizeof(int)+sizeof(int)+ sizeof(float) + sizeof(float)];
    char messageType = gkMessagePositionOfEnemy;
    [data appendBytes:&messageType length:1];
    self.playerlastUpdate++;
    int update = self.playerlastUpdate;
    [data appendBytes:&update length:sizeof(int)];

//preparing data for enemyInfo
    [data appendBytes:&enemyInfo.state length:sizeof(int)];
    [data appendBytes:&enemyInfo.position.x length:sizeof(float)];
    [data appendBytes:&enemyInfo.position.y length:sizeof(float)];

    NSLog(@"sendEnemyInformation: sending data = %@",data);

    [self.connectivityManager sendData:data withMode:unreliable];
}

- (void)sendToOpponentLevel:(int)level
               andCharacterType:(MessengerSpriteType)gameType
{
    NSMutableData *data = [NSMutableData dataWithCapacity:1+sizeof(int)+sizeof(int)];
    char messageType = gkMessageCharacterAndLevelToPlay;
    NSLog(@"level = %d, gameType= %d", level, gameType);
    
    [data appendBytes:&messageType length:1];
    [data appendBytes:&level length:sizeof(int)];
    [data appendBytes:&gameType length:sizeof(int)];
    NSLog(@"Sending level and character - data=%@", data);

    [self.connectivityManager sendData:data withMode:reliable];
}


- (void)sendThatThePlayerIsReadyToPlay
{
    NSMutableData *data = [NSMutableData dataWithCapacity:1];
    char messageType = gkMessageReadyToStart;
    [data appendBytes:&messageType length:1];

    NSLog(@"player is ready to play =%@", data);
    
    [self.connectivityManager sendData:data withMode:reliable];
}

- (void)dismissBrowserController
{
    [self.connectivityManager dismissBrowser];
}

#pragma mark - disconnecting
- (void)opponentWasDisconnected
{
    [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkNotificationNameForDisconnectedOpponent object:nil];

    if ( !self.isAlertViewDisplayedEndConnection ){
        self.isAlertViewDisplayedEndConnection = YES;
        UIAlertView *disconnectedAV = [[UIAlertView alloc] initWithTitle:kTitleDisconnectedOpponent message:kMessageDisconnectedOpponent delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil];
        [disconnectedAV show];
        if ( self.isEnteredBackground )
        {
            self.isEnteredBackground = NO;
            self.isAlertViewDisplayedEndConnection = NO;
        }
    }
}

#pragma mark - end session
- (void)endSession
{
    [self.connectivityManager endSession];
}

@end
